package iec104_client

import (
	iec104_apdu "go-iot-gateway/pkg/iec104/apdu"
	"time"
)

func readHandler(client *Iec104Client) {
	client.options.Logger.Info("Iec104 数据读协程启动")
	client.goroutineWaitGroup.Add(1)
	defer func() {
		client.contextCancelFunc()
		client.goroutineWaitGroup.Done()
		client.options.Logger.Info("Iec104 数据读协程 停止")
	}()

	go receiveMsg(client)

	for {
		select {
		case resp := <-client.readChan:
			go client.options.OnMessageHandler(resp)
		case <-client.context.Done():
			return
		}
	}

}

func receiveMsg(client *Iec104Client) {
	client.goroutineWaitGroup.Add(1)
	defer func() {
		client.contextCancelFunc()
		client.goroutineWaitGroup.Done()
		client.options.Logger.Info("Iec104 数据解析协程 停止")
	}()

	handleErr := func(tag string, err error) {
		client.options.Logger.Errorf("%s read socket读操作异常: %+v", tag, err)
	}

	for {
		select {
		case <-client.context.Done():
			return
		default:
		}

		//读取启动符和长度
		initiatorBuf := make([]byte, 2)
		readLen, err := client.conn.Read(initiatorBuf)
		if err != nil {
			//todo 尝试
			handleErr("读取启动符和长度出错", err)
			return
		}

		//设置TCP连接的超时时间, 如果在超时时间内没有收到任何数据，则连接将被关闭
		_ = client.conn.SetDeadline(time.Now().Add(client.options.NetConnDeadline))
		if readLen == 0 {
			//client.options.Logger.Debug("读取到空数据")
			time.Sleep(client.options.ReadRetryInterval)
			continue
		}

		//读取正文
		contentLen := int(initiatorBuf[1]) //正文长度
		contentBuf := make([]byte, contentLen)
		readLen, err = client.conn.Read(contentBuf)
		if err != nil {
			handleErr("读取正文出错", err)
			return
		}
		//长度不够继续读取，直至达到期望长度
		i := 1
		for readLen < contentLen {
			i++
			nextLength := contentLen - readLen
			nextBuf := make([]byte, nextLength)
			m, err := client.conn.Read(nextBuf)
			if err != nil {
				handleErr("循环读取正文出错", err)
				return
			}
			contentBuf = append(contentBuf[:readLen], nextBuf[:m]...)
			readLen = len(contentBuf)
			//client.options.Logger.Debugf("循环读取数据，当前为第%d次读取，期望长度:%d,本次长度:%d,当前总长度:%d", i, contentLen, m, readLen)
		}

		bytes := append(initiatorBuf, contentBuf[:readLen]...)

		//client.options.Logger.Debugf("收到原始数据 长度%d:  rsn:%d ssn:%d ", 2+len(contentBuf[:readLen]), client.rsn, client.ssn)
		client.options.Logger.Debugf("接收﹤﹤ % X", bytes)

		//解析
		apdu := new(iec104_apdu.APDU)

		err = apdu.Parse(contentBuf)
		if err != nil {
			client.options.Logger.Errorf("解析APDU异常: %v", err)
			continue
		}

		//默认报文处理
		printSendMsg(client.options.Logger, initiatorBuf, apdu)
		handleMsg(apdu, client)
	}

}

// 解析报文
func handleMsg(apdu *iec104_apdu.APDU, client *Iec104Client) {

	switch apdu.Frame.Type() {
	case iec104_apdu.FrameTypeI:
		handleIFrame(apdu, client)
	case iec104_apdu.FrameTypeS:
		handleSFrame(apdu, client)
	case iec104_apdu.FrameTypeU:
		handleUFrame(apdu, client)
	default:
		client.options.Logger.Errorf("接收到未知帧")
	}
}

func handleSFrame(apdu *iec104_apdu.APDU, client *Iec104Client) {
	//client.options.Logger.Debugln("接收到S帧")
}

func handleIFrame(apdu *iec104_apdu.APDU, client *Iec104Client) {
	//client.options.Logger.Debugln("接收到I帧")

	client.incrRsn()

	switch apdu.ASDU.TypeID {
	case iec104_apdu.MEiNA1:
		//client.options.Logger.Info("接收到初始化结束，开始发送总召唤")
		client.SendSFrame()
		client.SendTotalCall()
	case iec104_apdu.CIcNa1:
		if apdu.ASDU.Cot == 7 {
			//client.options.Logger.Info("接收总召唤确认帧")
			client.SendSFrame()
		} else if apdu.ASDU.Cot == 10 {
			//client.options.Logger.Info("接收总召唤结束帧")
			client.SendSFrame()
			//client.options.Logger.Info("发送电度总召唤")
			client.SendElectricityTotalCall()
		}
	case iec104_apdu.CCiNa1:
		if apdu.ASDU.Cot == 7 {
			client.options.Logger.Info("接收电度总召唤确认帧")
		} else if apdu.ASDU.Cot == 10 {
			client.options.Logger.Info("接收电度总召唤结束帧")
		}
		client.SendSFrame()
	default:
		client.iFrameNum++
		//client.options.Logger.Debugf("接收到第%d个I帧", client.iFrameNum)
		client.readChan <- apdu
		client.SendSFrame()
	}
}

func handleUFrame(apdu *iec104_apdu.APDU, client *Iec104Client) {
	//client.options.Logger.Debugln("接收到U帧")

	uFrame, ok := apdu.Frame.(*iec104_apdu.UFrame)
	if !ok {
		panic("无法转成u帧")
	}

	switch uFrame.Cmd {
	case iec104_apdu.UFrameFunctionStartDTC:
		//client.options.Logger.Info("U帧为启动确认帧，发送总召唤")
		//todo 总召间隔
		client.SendTotalCall()
	case iec104_apdu.UFrameFunctionTestFA:
		//client.options.Logger.Info("U帧为测试激活帧,发送测试确认帧")
		client.SendUFrame(iec104_apdu.UFrameFunctionTestFC)
	default:
		client.readChan <- apdu
	}

}
